package net.jangaroo.ide.idea; import com.intellij.javaee.facet.JavaeeFacet; import com.intellij.javaee.ui.packaging.ExplodedWarArtifactType; import com.intellij.javaee.ui.packaging.JavaeeFacetResourcesPackagingElement; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProvider; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.packaging.artifacts.Artifact; import com.intellij.packaging.artifacts.ArtifactManager; import com.intellij.packaging.artifacts.ModifiableArtifact; import com.intellij.packaging.artifacts.ModifiableArtifactModel; import com.intellij.packaging.elements.CompositePackagingElement; import com.intellij.packaging.elements.PackagingElement; import com.intellij.packaging.elements.PackagingElementResolvingContext; import com.intellij.packaging.impl.elements.ArchivePackagingElement; import com.intellij.packaging.impl.elements.ModuleOutputPackagingElement; import com.intellij.util.PairConsumer; import net.jangaroo.ide.idea.jps.JoocConfigurationBean; import net.jangaroo.ide.idea.jps.JpsJangarooSdkType; import net.jangaroo.jooc.config.PublicApiViolationsMode; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.maven.importing.FacetImporter; import org.jetbrains.idea.maven.importing.MavenRootModelAdapter; import org.jetbrains.idea.maven.model.MavenPlugin; import org.jetbrains.idea.maven.project.MavenConsole; import org.jetbrains.idea.maven.project.MavenEmbeddersManager; import org.jetbrains.idea.maven.project.MavenProject; import org.jetbrains.idea.maven.project.MavenProjectChanges; import org.jetbrains.idea.maven.project.MavenProjectsProcessorTask; import org.jetbrains.idea.maven.project.MavenProjectsTree; import org.jetbrains.idea.maven.project.SupportedRequestType; import org.jetbrains.idea.maven.utils.MavenJDOMUtil; import org.jetbrains.idea.maven.utils.MavenProcessCanceledException; import org.jetbrains.idea.maven.utils.MavenProgressIndicator; import org.jetbrains.idea.maven.utils.MavenUtil; import org.jetbrains.jps.model.java.JavaSourceRootType; import org.jetbrains.jps.model.module.JpsModuleSourceRootType; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import static net.jangaroo.ide.idea.jps.util.IdeaFileUtils.toIdeaUrl; /** * A Facet-from-Maven Importer for the Jangaroo Facet type. */ public class JangarooFacetImporter extends FacetImporter<JangarooFacet, JangarooFacetConfiguration, JangarooFacetType> { private static final String JANGAROO_MAVEN_PLUGIN_ARTIFACT_ID = "jangaroo-maven-plugin"; public static final String EXML_MAVEN_PLUGIN_ARTIFACT_ID = "exml-maven-plugin"; private static final String JANGAROO_PACKAGING_TYPE = "jangaroo"; public static final String DEFAULT_JANGAROO_FACET_NAME = "Jangaroo"; private static final String JOO_CLASSES_PATH = "joo/classes"; public JangarooFacetImporter() { super(JpsJangarooSdkType.JANGAROO_GROUP_ID, JANGAROO_MAVEN_PLUGIN_ARTIFACT_ID, JangarooFacetType.INSTANCE, DEFAULT_JANGAROO_FACET_NAME); } // we cannot use MavenProject#findPlugin(), because it also searches in <pluginManagement>: public static MavenPlugin findDeclaredJangarooPlugin(MavenProject mavenProject, @Nullable String artifactId) { for (MavenPlugin each : mavenProject.getDeclaredPlugins()) { if (each.getMavenId().equals(JpsJangarooSdkType.JANGAROO_GROUP_ID, artifactId)) { return each; } } return null; } public boolean isApplicable(MavenProject mavenProjectModel) { MavenPlugin jangarooMavenPlugin = findJangarooMavenPlugin(mavenProjectModel); if (jangarooMavenPlugin == null && JANGAROO_PACKAGING_TYPE.equals(mavenProjectModel.getPackaging())) { Notifications.Bus.notify(new Notification("jangaroo", "Jangaroo Facet not created/updated", "Module " + mavenProjectModel.getMavenId() + " uses packaging type 'jangaroo', " + "but no jangaroo-maven-plugin or exml-maven-plugin was found. Try repeating 'Reimport All Maven Projects'.", NotificationType.WARNING)); } // any of the two Jangaroo Maven plugins has to be configured explicitly: return jangarooMavenPlugin != null; } /** * Find jangaroo-maven-plugin, or, if not present, exml-maven-plugin, which also activates the * Jangaroo Maven lifecycle and thus the Jangaroo compiler. * @param mavenProjectModel IDEA's Maven project model * @return jangaroo-maven-plugin or exml-maven-plugin */ private MavenPlugin findJangarooMavenPlugin(MavenProject mavenProjectModel) { MavenPlugin jangarooPlugin = findDeclaredJangarooPlugin(mavenProjectModel, JANGAROO_MAVEN_PLUGIN_ARTIFACT_ID); if (jangarooPlugin == null) { jangarooPlugin = findDeclaredJangarooPlugin(mavenProjectModel, EXML_MAVEN_PLUGIN_ARTIFACT_ID); } return jangarooPlugin; } @Override public void getSupportedDependencyTypes(Collection<String> result, SupportedRequestType type) { super.getSupportedDependencyTypes(result, type); result.add(JANGAROO_PACKAGING_TYPE); } @Override protected void setupFacet(JangarooFacet f, MavenProject mavenProject) { //System.out.println("setupFacet called!"); } private String getConfigurationValue(MavenProject mavenProjectModel, String configName, @Nullable String defaultValue) { String value = null; Element compileConfiguration = mavenProjectModel.getPluginGoalConfiguration(JpsJangarooSdkType.JANGAROO_GROUP_ID, JANGAROO_MAVEN_PLUGIN_ARTIFACT_ID, "compile"); if (compileConfiguration != null) { Element compileConfigurationChild = compileConfiguration.getChild(configName); if (compileConfigurationChild != null) { value = compileConfigurationChild.getTextTrim(); } } if (value == null) { value = findGoalConfigValue(mavenProjectModel, "compile", configName); if (value == null) { value = findConfigValue(mavenProjectModel, configName); } } return value != null && value.length() > 0 ? value : defaultValue; } private boolean getBooleanConfigurationValue(MavenProject mavenProjectModel, String configName, boolean defaultValue) { String value = getConfigurationValue(mavenProjectModel, configName, String.valueOf(defaultValue)); return Boolean.valueOf(value); } @Override protected void reimportFacet(IdeModifiableModelsProvider modelsProvider, Module module, MavenRootModelAdapter rootModel, JangarooFacet jangarooFacet, MavenProjectsTree mavenTree, MavenProject mavenProjectModel, MavenProjectChanges changes, Map<MavenProject, String> mavenProjectToModuleName, List<MavenProjectsProcessorTask> postTasks) { //System.out.println("reimportFacet called!"); JangarooFacetConfiguration jangarooFacetConfiguration = jangarooFacet.getConfiguration(); JoocConfigurationBean jooConfig = jangarooFacetConfiguration.getState(); MavenPlugin jangarooMavenPlugin = findJangarooMavenPlugin(mavenProjectModel); String jangarooSdkVersion = jangarooMavenPlugin.getVersion(); String sdkHomePath = jangarooSdkHomePath(JpsJangarooSdkType.JANGAROO_COMPILER_API_ARTIFACT_ID, jangarooSdkVersion); Sdk jangarooSdk = JangarooSdkUtils.createOrGetSdk(JangarooSdkType.getInstance(), sdkHomePath); if (jangarooSdk == null) { if (jangarooSdkVersion == null) { Notifications.Bus.notify(new Notification("Maven", "Jangaroo Version Not Found", "No version found for Jangaroo SDK in Maven POM " + mavenProjectModel.getDisplayName() + ", no Jangaroo facet created.", NotificationType.WARNING)); return; } jooConfig.jangarooSdkName = "Jangaroo SDK " + jangarooSdkVersion; } else { jooConfig.jangarooSdkName = jangarooSdk.getName(); if (jangarooSdk.getVersionString() == null) { Notifications.Bus.notify(new Notification("Jangaroo", "Incompatible Jangaroo Version", "Jangaroo compiler version " + jangarooSdkVersion + " is not compatible with this Jangaroo IDEA plugin. " + "Either use another IDEA plugin or change the Jangaroo Maven plugin version in " + mavenProjectModel.getDisplayName(), NotificationType.WARNING)); return; } jangarooSdkVersion = jangarooSdk.getVersionString(); } jooConfig.allowDuplicateLocalVariables = getBooleanConfigurationValue(mavenProjectModel, "allowDuplicateLocalVariables", jooConfig.allowDuplicateLocalVariables); jooConfig.verbose = getBooleanConfigurationValue(mavenProjectModel, "verbose", false); jooConfig.enableAssertions = getBooleanConfigurationValue(mavenProjectModel, "enableAssertions", false); // "debug" (boolean; true), "debuglevel" ("none", "lines", "source"; "source") boolean isWar = "war".equals(mavenProjectModel.getPackaging()); String outputDirectory = findConfigValue(mavenProjectModel, "outputDirectory"); if (outputDirectory == null) { outputDirectory = isWar ? "target/jangaroo-output" : mavenProjectModel.getOutputDirectory(); } File outputDir = new File(outputDirectory); if (!outputDir.isAbsolute()) { outputDir = new File(mavenProjectModel.getDirectory(), outputDirectory); } String jooClassesPath = JOO_CLASSES_PATH; boolean isJangaroo2or3 = jangarooSdkVersion != null && (jangarooSdkVersion.startsWith("2.") || jangarooSdkVersion.startsWith("3")); if (isJangaroo2or3 && !isWar) { jooClassesPath = "META-INF/resources/" + jooClassesPath; } jooConfig.outputDirectory = toIdeaUrl(new File(outputDir, jooClassesPath).getAbsolutePath()); String apiOutputDirectory = getConfigurationValue(mavenProjectModel, "apiOutputDirectory", null); jooConfig.apiOutputDirectory = isWar ? null : toIdeaUrl(apiOutputDirectory != null ? apiOutputDirectory : new File(outputDir, "META-INF/joo-api").getAbsolutePath()); String testOutputDirectory = findConfigValue(mavenProjectModel, "testOutputDirectory"); if (testOutputDirectory == null) { testOutputDirectory = isWar ? "target/jangaroo-test-output" : mavenProjectModel.getTestOutputDirectory(); } File testOutputDir = new File(testOutputDirectory); if (!testOutputDir.isAbsolute()) { testOutputDir = new File(mavenProjectModel.getDirectory(), testOutputDirectory); } jooConfig.testOutputDirectory = toIdeaUrl(new File(testOutputDir, JOO_CLASSES_PATH).getAbsolutePath()); String publicApiViolationsMode = getConfigurationValue(mavenProjectModel, "publicApiViolations", "warn"); try { jooConfig.publicApiViolationsMode = PublicApiViolationsMode.valueOf(publicApiViolationsMode.toUpperCase()); } catch (IllegalArgumentException e) { Notifications.Bus.notify(new Notification("Maven", "Invalid Jangaroo Configuration", "Illegal value for <publicApiViolations>: '" + publicApiViolationsMode + "' in Maven POM " + mavenProjectModel.getDisplayName() +", falling back to 'warn'.", NotificationType.WARNING)); jooConfig.publicApiViolationsMode = PublicApiViolationsMode.WARN; } if (isWar) { postTasks.add(new AddJangarooPackagingOutputToExplodedWebArtifactsTask(jangarooFacet)); } } private static String jangarooSdkHomePath(String artifactId, String version) { File localRepository = MavenUtil.resolveLocalRepository(null, null, null); File jarFile = JpsJangarooSdkType.getJangarooArtifact(localRepository, artifactId, version); return jarFile.getParentFile().getAbsolutePath(); } @Override public void collectSourceRoots(MavenProject mavenProject, PairConsumer<String, JpsModuleSourceRootType<?>> result) { collectSourceOrTestFolders(mavenProject, JavaSourceRootType.SOURCE, "compile", "src/main/joo", result); collectSourceOrTestFolders(mavenProject, JavaSourceRootType.TEST_SOURCE, "testCompile", "src/test/joo", result); } private void collectSourceOrTestFolders(MavenProject mavenProject, JavaSourceRootType type, String goal, String defaultDir, PairConsumer<String, JpsModuleSourceRootType<?>> result) { Element goalConfiguration = getGoalConfig(mavenProject, goal); if (goalConfiguration != null) { List<String> mvnSrcDirs = MavenJDOMUtil.findChildrenValuesByPath(goalConfiguration, "sources", "directory"); if (!mvnSrcDirs.isEmpty()) { for (String mvnSrcDir : mvnSrcDirs) { result.consume(mvnSrcDir, type); } return; } } result.consume(defaultDir, type); } private static class AddJangarooPackagingOutputToExplodedWebArtifactsTask implements MavenProjectsProcessorTask { private final JangarooFacet jangarooFacet; private AddJangarooPackagingOutputToExplodedWebArtifactsTask(JangarooFacet jangarooFacet) { this.jangarooFacet = jangarooFacet; } public void perform(final Project project, MavenEmbeddersManager mavenEmbeddersManager, MavenConsole mavenConsole, MavenProgressIndicator mavenProgressIndicator) throws MavenProcessCanceledException { ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { Module webModule = jangarooFacet.getModule(); // for this Jangaroo-enabled Web app, add all Jangaroo-dependent modules' Jangaroo compiler output. // find the IDEA exploded Web artifact for this Jangaroo-enabled Web app module: final Artifact artifact = getExplodedWebArtifact(webModule); if (artifact != null) { final ArtifactManager artifactManager = ArtifactManager.getInstance(project); // add the remaining modules' Jangaroo compile output to the Web app's root directory: CompositePackagingElement<?> webInfDir = artifact.getRootElement().findCompositeChild("WEB-INF"); if (webInfDir != null) { CompositePackagingElement<?> libDir = webInfDir.findCompositeChild("lib"); if (libDir != null) { PackagingElementResolvingContext resolvingContext = artifactManager.getResolvingContext(); Collection<ArchivePackagingElement> jangarooArchives = findJangarooArchives(resolvingContext, libDir); if (!jangarooArchives.isEmpty()) { createJangarooPackagingOutputElements(artifact, resolvingContext, jangarooArchives); libDir.removeChildren(jangarooArchives); ensureBuildOnMake(artifact, artifactManager); } } } } } }); } private static void createJangarooPackagingOutputElements(Artifact artifact, PackagingElementResolvingContext resolvingContext, Collection<ArchivePackagingElement> jangarooArchives) { CompositePackagingElement<?> rootBeer = artifact.getRootElement(); for (ArchivePackagingElement archivePackagingElement : jangarooArchives) { Module jarModule = getModuleOfArchive(archivePackagingElement, resolvingContext); JangarooFacet jangarooFacet = JangarooFacet.ofModule(jarModule); PackagingElement<?> jangarooPackagingOutputElement = new JangarooPackagingOutputElement(resolvingContext.getProject(), jangarooFacet); rootBeer.addOrFindChild(jangarooPackagingOutputElement); } } private static void ensureBuildOnMake(final Artifact artifact, final ArtifactManager artifactManager) { // instruct IDEA to build the Web app on make: if (!artifact.isBuildOnMake()) { ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { ModifiableArtifactModel modifiableArtifactModel = artifactManager.createModifiableModel(); final ModifiableArtifact modifiableArtifact = modifiableArtifactModel.getOrCreateModifiableArtifact(artifact); modifiableArtifact.setBuildOnMake(true); modifiableArtifactModel.commit(); } }); } }); } } private static Collection<ArchivePackagingElement> findJangarooArchives(PackagingElementResolvingContext resolvingContext, CompositePackagingElement<?> directory) { Collection<ArchivePackagingElement> toBeRemovedLibraries = new ArrayList<ArchivePackagingElement>(); for (PackagingElement packagingElement : directory.getChildren()) { if (packagingElement instanceof ArchivePackagingElement) { ArchivePackagingElement archivePackagingElement = (ArchivePackagingElement)packagingElement; Module module = getModuleOfArchive(archivePackagingElement, resolvingContext); if (JangarooFacet.ofModule(module) != null) { toBeRemovedLibraries.add(archivePackagingElement); } } } return toBeRemovedLibraries; } private static Module getModuleOfArchive(ArchivePackagingElement archivePackagingElement, PackagingElementResolvingContext resolvingContext) { List<PackagingElement<?>> archiveChildren = archivePackagingElement.getChildren(); if (archiveChildren.size() == 1) { PackagingElement<?> moduleOutputPackagingElement = archiveChildren.get(0); if (moduleOutputPackagingElement instanceof ModuleOutputPackagingElement) { return ((ModuleOutputPackagingElement)moduleOutputPackagingElement).findModule(resolvingContext); } } return null; } private static Artifact getExplodedWebArtifact(Module module) { ArtifactManager artifactManager = ArtifactManager.getInstance(module.getProject()); for (Artifact artifact : artifactManager.getArtifactsByType(ExplodedWarArtifactType.getInstance())) { Module artifactModule = findModule(artifactManager, artifact); if (module.equals(artifactModule)) { return artifact; } } return null; } private static @Nullable Module findModule(@NotNull ArtifactManager artifactManager, @NotNull Artifact artifact) { PackagingElementResolvingContext packagingElementResolvingContext = artifactManager.getResolvingContext(); for (PackagingElement<?> packagingElement : artifact.getRootElement().getChildren()) { if (packagingElement instanceof JavaeeFacetResourcesPackagingElement) { JavaeeFacet facet = ((JavaeeFacetResourcesPackagingElement) packagingElement).findFacet(packagingElementResolvingContext); if (facet != null) { return facet.getModule(); } } } return null; } } }